package inset;

import geom.Line;
import geom.Polygon;
import geom.Ray;

import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Iterator;


public class StraightSkeleton
{
    public static Ray calculateBisector(Line l1, Line l2)
    {
        l1 = (Line) l1.clone();
        l2 = (Line) l2.clone();

        // Determine which endpoints are the common ones
        if (l1.getP1().equals(l2.getP1()))
        {
            // Do nothing
        }
        else if (l1.getP1().equals(l2.getP2()))
        {
            System.out.println("SWAPPED BOTH");
            swapLineEndpoints(l1);
            swapLineEndpoints(l2);
        }
        else if (l1.getP2().equals(l2.getP2()))
        {
            System.out.println("SWAPPED 2");
            swapLineEndpoints(l2);
        }
        else if (l1.getP2().equals(l2.getP1()))
        {
            System.out.println("SWAPPED 1");
            swapLineEndpoints(l1);
        }
        else
        {
            System.err.println("No common endpoints");
        }

        Ray r1 = new Ray(l1);
        Ray r2 = new Ray(l2);

        Ray bisector = r1.determineBisector(r2);

        return bisector;
    }

    public static ArrayList<Ray> calculateBisectors(Polygon poly)
    {
        ArrayList<Line> polyLines = poly.getLineSegments();
        ArrayList<Ray> bisectors = new ArrayList<Ray>();

        for (int i = 0, j = 1; i < polyLines.size(); i++, j++)
        {
            if (j == polyLines.size()) j = 0;
                
            Ray bisector = calculateBisector(polyLines.get(i), polyLines.get(j));
            
            if (!poly.contains(bisector.getEndpoint()))
                bisector.flip();

            bisectors.add(bisector);
        }

        return bisectors;
    }

    private static void swapLineEndpoints(Line line)
    {
        line.setLine(line.getP2(), line.getP1());
    }

    public static Polygon createInsetPoly(Polygon poly, double dist)
    {
        // GeneralPath gp = new GeneralPath();
        Polygon inset = new Polygon();

        ArrayList<Line> polyLines = poly.getLineSegments();       

        ArrayList<Ray> bisectors = calculateBisectors(poly);

        if (bisectors.size() == 0) return inset;

        // The first ray needs to start at the first bisector
        // The first stuff is calculated last, so the last bisector (edge,
        // edge+1)
        Ray previous = getInitialRay(polyLines.get(0), bisectors.get(0), dist);

        // Look backwards to see if we need to be extended
        Ray prevTemp = (Ray) previous.clone();
        prevTemp.flip();
        Point2D intPt = prevTemp
                        .intersects(bisectors.get(bisectors.size() - 1));
        Point2D endPt = prevTemp.getEndpoint();

        prevTemp.flip();
        prevTemp.setOrigin(intPt);
        prevTemp.setRayLength(intPt.distance(endPt));

        inset.moveTo(prevTemp.getOrigin());
        inset.lineTo(prevTemp.getEndpoint());

        Iterator<Ray> bisector = bisectors.iterator();
        Iterator<Line> line = polyLines.iterator();

        while (line.hasNext() && bisector.hasNext())
        {
            Line origLine = line.next();
            Ray newSegmentRay = getOffsetRay(origLine, bisector.next(),
                            previous, dist);

            inset.lineTo(newSegmentRay.getEndpoint());

            previous = newSegmentRay;
        }

        inset.close();

        inset = unComplexify(inset);

        return inset;
    }

    /**
     * Remove redundant vertices
     * 
     * @param p
     * @return
     */
    public static Polygon unComplexify(Polygon p)
    {
        ArrayList<Line> segs = p.getLineSegments();

        Polygon a = new Polygon();

        for (int i = 0, j = 1; i < segs.size(); i++, j++)
        {
            if (j >= segs.size()) j = 0;

            Line l1 = segs.get(i);
            Line l2 = segs.get(j);

            {
                Ray bisector = calculateBisector(l1, l2);

                if (Double.isNaN(bisector.getDirection().x)
                                && Double.isNaN(bisector.getDirection().y))
                {
                    // No angle so remove lines and add a new longer
                    // one
                    segs.remove(l1);
                    segs.remove(l2);

                    Line l = new Line(l1.getP1(), l2.getP2());
                    segs.add(l);
                }
            }
        }

        // Add this list to the polygon
        Line ln = segs.get(0);
        a.moveTo(ln.getP1());
        a.lineTo(ln.getP2());

        for (Line l : segs)
        {
            a.lineTo(l.getP2());
        }

        a.close();

        a.ensureWindingDirection(p.getWindingDirection());

        return a;
    }

    private static boolean validateSegment(Line originalSeg, Ray bisector1,
                    Ray bisector2, double offsetDist)
    {
        Point2D bisectInt = bisector1.intersects(bisector2);

        if (bisectInt != null)
        {
            double bisectDist = originalSeg.ptSegDistSq(bisectInt);

            return bisectDist <= offsetDist;
        }

        return false;
    }

    private static Ray getOffsetRay(Line line, Ray bisector, Ray previousRay,
                    double dist)
    {
        // Offseting ray
        Ray r = new Ray(line);
        r.perpendicular();
        r.normalize();
        r.setRayLength(dist);

        // The starting point MUST be the old ray end. The ending point is the
        // end of the current line, offset
        AffineTransform at = AffineTransform.getTranslateInstance(r
                        .getDirection().x, r.getDirection().y);
        Ray nr = new Ray(new Line(previousRay.getEndpoint(), at.transform(line
                        .getP2(), null)));
        Point2D pt = nr.intersects(bisector);

        System.out.println(nr + " " + pt + " " + nr.getOrigin());
        nr.setRayLength(pt.distance(nr.getOrigin()));

        return nr;
    }

    private static Ray getInitialRay(Line line, Ray bisector, double dist)
    {
        // Offseting ray
        Ray r = new Ray(line);
        r.perpendicular();
        r.normalize();
        r.setRayLength(dist);

        // The starting point doesn't matter. The end point must be the
        // beginning of the next offset segment
        AffineTransform at = AffineTransform.getTranslateInstance(r
                        .getDirection().x, r.getDirection().y);
        Ray nr = new Ray(new Line(at.transform(line.getP1(), null), at
                        .transform(line.getP2(), null)));
        Point2D pt = nr.intersects(bisector);

        if (pt == null)
        {
            bisector.flip();
            pt = nr.intersects(bisector);
        }

        System.out.println(nr + " " + pt + " " + nr.getOrigin());
        nr.setRayLength(pt.distance(nr.getOrigin()));

        return nr;
    }
}